//
//  Problem46.swift  ✅
//  TestProject 46. 全排列
//
//  Created by 武侠 on 2020/11/10.
//  Copyright © 2020 zhulong. All rights reserved.
//

import UIKit

/*
 46. 全排列
 给定一个 没有重复 数字的序列，返回其所有可能的全排列。

 示例:
 输入: [1,2,3]
 输出:
 [
   [1,2,3],
   [1,3,2],
   [2,1,3],
   [2,3,1],
   [3,1,2],
   [3,2,1]
 ]
 */
@objcMembers class Problem46: NSObject {
    func solution() {
        print(permute([2,3,5]))
        
        print(permute([1,1,0,0,1,-1,-1,1]))
    }
    
    
    func permute(_ nums: [Int]) -> [[Int]] {
        if nums.count == 0 {
            return []
        }
        
        // 存储所有的结果的数据
        var lists:[[Int]] = []
        
        // 存储当前排列的数组
        var path:[Int] = []
        
        // 判断数组中，哪个值存储到了path中
        var used:[Bool] = []
        for _ in nums {
            used.append(false)
        }
        
        combinaNext(nums, &path, &lists, &used)
        return lists
    }
    
    func combinaNext(_ candidates:[Int], _ path: inout [Int], _ lists: inout[[Int]], _ used: inout [Bool]) {
        
        // 判断是否存储了所有的数字
        if path.count == candidates.count {
            if lists.contains(path) == false {
                lists.append(path)
            }
            return
        }
        
        // 从0开始：因为过滤掉用，已经加入到path中的位置
        for i in 0..<candidates.count {
            
            // 过滤
            if used[i] == true {
                continue
            }
            
            path.append(candidates[i])
            used[i] = true
            
            combinaNext(candidates, &path, &lists, &used)
            
            // 回溯
            path.removeLast()
            used[i] = false
        }
        
    }
    
}

/*
 思想：回溯法
 回溯算法与深度优先遍历
 回溯法 采用试错的思想，它尝试分步的去解决一个问题。在分步解决问题的过程中，当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候，它将取消上一步甚至是上几步的计算，再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现，在反复重复上述的步骤后可能出现两种情况：
        1:找到一个可能存在的正确的答案；
        2:在尝试了所有可能的分步方法后宣告该问题没有答案。
 
 深度优先搜索 算法（英语：Depth-First-Search，DFS）是一种用于遍历或搜索树或图的算法。这个算法会 尽可能深的搜索树的分支。当结点 v 的所在边都己被探寻过，搜索将 回溯 到发现结点 v 的那条边的起始结点。这一过程一直进行到已发现从源结点可达的所有结点为止。如果还存在未被发现的结点，则选择其中一个作为源结点并重复以上过程，整个进程反复进行直到所有结点都被访问为止。

 我刚开始学习「回溯算法」的时候觉得很抽象，一直不能理解为什么递归之后需要做和递归之前相同的逆向操作，在做了很多相关的问题以后，我发现其实「回溯算法」与「深度优先遍历」有着千丝万缕的联系

 个人理解
 我个人的理解是：「回溯算法」强调了「深度优先遍历」思想的用途，用一个 不断变化的变量，在尝试各种可能的过程中，搜索需要的结果。强调了 回退操作对于搜索的合理性。而「深度优先遍历」强调一种遍历的思想，与之对应的遍历思想是「广度优先遍历」。至于广度优先遍历为什么没有成为强大的搜索算法，我们在题解后面会提。

 在「力扣」第 51 题的题解《回溯算法（第 46 题 + 剪枝）》 中，展示了如何使用回溯算法搜索 44 皇后问题的一个解，相信对大家直观地理解「回溯算法」是有帮助。

 搜索与遍历
 我们每天使用的搜索引擎帮助我们在庞大的互联网上搜索信息。搜索引擎的「搜索」和「回溯搜索」算法里「搜索」的意思是一样的。

 搜索问题的解，可以通过 遍历 实现。所以很多教程把「回溯算法」称为爆搜（暴力解法）。因此回溯算法用于 搜索一个问题的所有的解，通过深度优先遍历的思想实现。

 与动态规划的区别
 共同点：用于求解多阶段决策问题。多阶段决策问题即：
    求解一个问题分为很多步骤（阶段）；
    每一个步骤（阶段）可以有多种选择。
 不同点：动态规划只需要求我们评估最优解是多少，最优解对应的具体解是什么并不要求。因此很适合应用于评估一个方案的效果；
 回溯算法可以搜索得到所有的方案（当然包括最优解），但是本质上它是一种遍历算法，时间复杂度很高。
 从全排列问题开始理解回溯算法
 我们尝试在纸上写 33 个数字、44 个数字、55 个数字的全排列，相信不难找到这样的方法。以数组 [1, 2, 3] 的全排列为例。

 先写以 11 开头的全排列，它们是：[1, 2, 3], [1, 3, 2]，即 1 + [2, 3] 的全排列（注意：递归结构体现在这里）；
 再写以 22 开头的全排列，它们是：[2, 1, 3], [2, 3, 1]，即 2 + [1, 3] 的全排列；
 最后写以 33 开头的全排列，它们是：[3, 1, 2], [3, 2, 1]，即 3 + [1, 2] 的全排列。
 总结搜索的方法：按顺序枚举每一位可能出现的情况，已经选择的数字在 当前 要选择的数字中不能出现。按照这种策略搜索就能够做到 不重不漏。这样的思路，可以用一个树形结构表示。

 看到这里的朋友，建议先尝试自己画出「全排列」问题的树形结构。

 */
